package com.zheng.study.web.shiro.filter;

import com.zheng.study.base.bean.Result;
import com.zheng.study.base.bean.SystemConfig;
import com.zheng.study.base.utils.HttpRequestUtil;
import com.zheng.study.base.utils.LogUtil;
import com.zheng.study.company.entity.Admin;
import com.zheng.study.company.service.AdminService;
import com.zheng.study.company.service.UserService;
import com.zheng.study.web.exception.UserExpiredException;
import com.zheng.study.web.shiro.Constants;
import com.zheng.study.web.shiro.codec.HmacSHA256Utils;
import com.zheng.study.web.shiro.realm.StatelessLoginToken;
import com.zheng.study.web.shiro.utils.StrutsUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信的绑定动作和用户验证
 * ============================================================================
 * author : fallenpanda
 * createDate:  2018/8/20 。
 * ============================================================================
 */
public class WxFormAuthcFilter extends AccessControlFilter {

	@Resource
	private UserService userService;
	@Resource
	private AdminService adminService;
	@Resource
	private SystemConfig systemConfig;

	protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
		return false;
	}

	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

		String username = (String) request.getAttribute(Constants.REQUEST_PARAMETER_USERNAME);
		String password = (String) request.getAttribute(Constants.REQUEST_PARAMETER_PASSWORD);

		//生成无状态Token
		StatelessLoginToken token = new StatelessLoginToken(username, password);
		Subject subject = getSubject(request, response);
		Boolean flag=true;
		try {
			//5、委托给Realm进行登录
			subject.login(token);
			flag=onLoginSuccess(token,subject,request, response);
		} catch (Exception e) {
			String error = null;
			if( e instanceof UnknownAccountException){
				error = "用户名/密码错误";
			}else if(e instanceof IncorrectCredentialsException){
				error = "用户名/密码错误";
			}else if(e instanceof ExcessiveAttemptsException){
				error = "密码输入错误超过5次，请10分钟后再试";
			}else if(e instanceof UserExpiredException) {
				return onLoginSuccess(token,subject,request, response);
			}else if(e instanceof AuthenticationException){
				error = "您的账号没有访问权限";
			}else if (e instanceof DisabledAccountException){
				error = "账号禁用" ;
			}else if (e instanceof LockedAccountException){
				error = "账号锁定" ;
			}else{
				error = "账号异常";
			}
			onLoginFail(request,response,error); //6、登录失败
			return false;
		}
		return flag;
	}

	private boolean onLoginSuccess(StatelessLoginToken token, Subject subject, ServletRequest request, ServletResponse response) throws IOException {

		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		StatelessLoginToken formToken = (StatelessLoginToken) token;
		String openId = request.getParameter(Constants.PARAM_OPENID);
		Admin admin = adminService.getAdminByAccount((String) token.getPrincipal());
		Result result = userService.linkUser(httpServletResponse,admin, openId);
		if(result.getState() == 1){
			//清除的是response的cookie，如果request的cookie还在，在login中的Action还会存在，但是response已经删除了
			StrutsUtil.deleteCookie(httpServletRequest, httpServletResponse, Constants.PARAM_USERNAME);
			StrutsUtil.deleteCookie(httpServletRequest, httpServletResponse, Constants.PARAM_DIGEST);

			//生成cookie
			Map<String, String> map = new HashMap<String,String>();
			map.put(Constants.PARAM_OPENID, openId);
			map.put(Constants.PARAM_USERNAME, formToken.getUsername());

			//response中生效
			StrutsUtil.addCookie(httpServletResponse, Constants.PARAM_DIGEST, HmacSHA256Utils.digest(admin.getSalt(), map));
			StrutsUtil.addCookie(httpServletResponse, Constants.PARAM_USERNAME, formToken.getUsername());

			//更新注册用户openId
			String HOSP_CODE = systemConfig.getHosp_code();
			String params = "HOSP_CODE="+HOSP_CODE+"&HOSP_USER_CODE="+String.valueOf(token.getPrincipal())+"&OPENID="+openId;
			String url = systemConfig.getWeixin_bridge_url()+"/updateOpenId.htm";
			String rs = new HttpRequestUtil().sendHttpRequest(url, params, "application/x-www-form-urlencoded");
		}else if(result.getState()==2){
			String error="该微信号已绑定其它账号";
			onLoginFail(request,response,error); //6、登录失败
			return  false;
		} else{
			String error="该帐号已绑定微信号";
			onLoginFail(request,response,error); //6、登录失败
			return  false;
		}
		return true;
	}

	//登录失败时默认返回401状态码
	private void onLoginFail(ServletRequest request, ServletResponse response, String error) throws IOException {
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		httpResponse.addHeader(Constants.SHIRO_AUTHC_ERROR,error);
		String login = systemConfig.getWeixin_bridge_url()+"/login.htm?error="+error;
		setLoginUrl(httpResponse.encodeURL(login));
		httpResponse.sendRedirect(login);
	}

	//下面是继承来源于AccessControlFilter的方法重写
	/**
	 * Simple default login URL equal to <code>/login.jsp</code>, which can be overridden by calling the
	 * {@link #setLoginUrl(String) setLoginUrl} method.
	 */
	public static final String DEFAULT_LOGIN_URL = "/login.jsp";

	/**
	 * Constant representing the HTTP 'GET' request method, equal to <code>GET</code>.
	 */
	public static final String GET_METHOD = "GET";

	/**
	 * Constant representing the HTTP 'POST' request method, equal to <code>POST</code>.
	 */
	public static final String POST_METHOD = "POST";

	/**
	 * The login url to used to authenticate a user, used when redirecting users if authentication is required.
	 */
	private String loginUrl = DEFAULT_LOGIN_URL;

	/**
	 * Returns the login URL used to authenticate a user.
	 * <p/>
	 * Most Shiro filters use this url
	 * as the location to redirect a user when the filter requires authentication.  Unless overridden, the
	 * {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed, which can be overridden via
	 * {@link #setLoginUrl(String) setLoginUrl}.
	 *
	 * @return the login URL used to authenticate a user, used when redirecting users if authentication is required.
	 */
	public String getLoginUrl() {
		return loginUrl;
	}

	/**
	 * Sets the login URL used to authenticate a user.
	 * <p/>
	 * Most Shiro filters use this url as the location to redirect a user when the filter requires
	 * authentication.  Unless overridden, the {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed.
	 *
	 * @param loginUrl the login URL used to authenticate a user, used when redirecting users if authentication is required.
	 */
	public void setLoginUrl(String loginUrl) {
		this.loginUrl = loginUrl;
	}

	/**
	 * Convenience method that acquires the Subject associated with the request.
	 * <p/>
	 * The default implementation simply returns
	 * {@link SecurityUtils#getSubject() SecurityUtils.getSubject()}.
	 *
	 * @param request  the incoming <code>ServletRequest</code>
	 * @param response the outgoing <code>ServletResponse</code>
	 * @return the Subject associated with the request.
	 */
	protected Subject getSubject(ServletRequest request, ServletResponse response) {
		return SecurityUtils.getSubject();
	}

	/**
	 * Returns <code>true</code> if
	 * {@link #isAccessAllowed(ServletRequest, ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
	 * otherwise returns the result of
	 * {@link #onAccessDenied(ServletRequest, ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
	 *
	 * @return <code>true</code> if
	 *         {@link #isAccessAllowed(ServletRequest, ServletResponse, Object) isAccessAllowed},
	 *         otherwise returns the result of
	 *         {@link #onAccessDenied(ServletRequest, ServletResponse) onAccessDenied}.
	 * @throws Exception if an error occurs.
	 */
	public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
		request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.FALSE);
		return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
	}

	/**
	 * Returns <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
	 * <p/>
	 * The default implementation merely returns <code>true</code> if the incoming request matches the configured
	 * {@link #getLoginUrl() loginUrl} by calling
	 * <code>{@link #pathsMatch(String, String) pathsMatch(loginUrl, request)}</code>.
	 *
	 * @param request  the incoming <code>ServletRequest</code>
	 * @param response the outgoing <code>ServletResponse</code>
	 * @return <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
	 */
	protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
		return pathsMatch(getLoginUrl(), request);
	}

	/**
	 * Convenience method for subclasses to use when a login redirect is required.
	 * <p/>
	 * This implementation simply calls {@link #saveRequest(ServletRequest) saveRequest(request)}
	 * and then {@link #redirectToLogin(ServletRequest, ServletResponse) redirectToLogin(request,response)}.
	 *
	 * @param request  the incoming <code>ServletRequest</code>
	 * @param response the outgoing <code>ServletResponse</code>
	 * @throws IOException if an error occurs.
	 */
	protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
		saveRequest(request);
		redirectToLogin(request, response);
	}

	/**
	 * Convenience method merely delegates to
	 * {@link WebUtils#saveRequest(ServletRequest) WebUtils.saveRequest(request)} to save the request
	 * state for reuse later.  This is mostly used to retain user request state when a redirect is issued to
	 * return the user to their originally requested url/resource.
	 * <p/>
	 * If you need to save and then immediately redirect the user to login, consider using
	 * {@link #saveRequestAndRedirectToLogin(ServletRequest, ServletResponse)
	 * saveRequestAndRedirectToLogin(request,response)} directly.
	 *
	 * @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
	 */
	protected void saveRequest(ServletRequest request) {
		WebUtils.saveRequest(request);
	}

	/**
	 * Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
	 * the request to that url.
	 * <p/>
	 * <b>N.B.</b>  If you want to issue a redirect with the intention of allowing the user to then return to their
	 * originally requested URL, don't use this method directly.  Instead you should call
	 * {@link #saveRequestAndRedirectToLogin(ServletRequest, ServletResponse)
	 * saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
	 * be reconstructed and re-used after a successful login.
	 *
	 * @param request  the incoming <code>ServletRequest</code>
	 * @param response the outgoing <code>ServletResponse</code>
	 * @throws IOException if an error occurs.
	 */
	protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
		String loginUrl = getLoginUrl();
		WebUtils.issueRedirect(request, response, loginUrl);
	}
}
